﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;

namespace DotNetCommon.Extensions;

/// <summary>
/// string 的扩展类。
/// </summary>
public static class StringExtensions
{
    #region 文件名/路径名有效验证
    /*
     * window下 Path.GetInvalidFileNameChars() 返回41个无效字符，Path.GetInvalidPathChars() 返回36个无效字符
     * linux下  Path.GetInvalidFileNameChars() 返回2个无效字符，Path.GetInvalidPathChars() 返回1个无效字符
     * 所以，为了文件和路径命名更加安全，统一文件命名采用window下的41个无效字符、路径命名采用window下的36个无效字符!
     */

    private static readonly char[] InvalidFileNameCharacters = ['"', '<', '>', '|', '\0', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\a', '\b', '\t', '\n', '\v', '\f', '\r', '\u000e', '\u000f', '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016', '\u0017', '\u0018', '\u0019', '\u001a', '\u001b', '\u001c', '\u001d', '\u001e', '\u001f', ':', '*', '?', '\\', '/'];
    private static readonly char[] InvalidPathCharacters = ['"', '<', '>', '|', '\0', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\a', '\b', '\t', '\n', '\v', '\f', '\r', '\u000e', '\u000f', '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016', '\u0017', '\u0018', '\u0019', '\u001a', '\u001b', '\u001c', '\u001d', '\u001e', '\u001f'];

    /// <summary>
    /// 确保给定的 <paramref name="fileName"/> 可以用作文件名。
    /// </summary>
    /// <remarks>
    /// 注意：这里统一使用window平台下(比较严格)41个无效的字符判断，其中包含(/\:*?"&lt;>|)
    /// </remarks>
    public static bool IsValidFileName(this string fileName) =>
        fileName.IsNotNullOrWhiteSpace() && fileName.IndexOfAny(InvalidFileNameCharacters) == -1;

    /// <summary>
    /// 确保给定的 <paramref name="path"/> 可以用作路径
    /// </summary>
    /// <remarks>
    /// 注意: 是文件夹路径,不是url路径 <br />
    /// 这里统一使用window平台下(比较严格)36个无效的字符判断
    /// </remarks>
    public static bool IsValidPathName(this string path) =>
        path.IsNotNullOrWhiteSpace() && path.IndexOfAny(InvalidPathCharacters) == -1;
    #endregion

    /// <summary>
    /// string.Format 的简化方法
    /// <example>
    /// <para>示例：</para>
    /// <code>
    /// "{0} is {1}".Format("He","Tom");
    /// //Output: "He is Tom"
    /// </code>
    /// </example>
    /// </summary>    
    public static string Format(this string value, params string[] paras)
    {
        if (value is null) return value;
        return string.Format(value, paras);
    }

    /// <summary>
    /// string.IsNullOrEmpty 的简化方法
    /// </summary>
    public static bool IsNullOrEmpty(this string value) => string.IsNullOrEmpty(value);

    /// <summary>
    /// string.IsNullOrEmpty 的简化方法(逆向)
    /// </summary>
    public static bool IsNotNullOrEmpty(this string value) => !value.IsNullOrEmpty();

    /// <summary>
    /// string.IsNullOrWhiteSpace 的简化方法
    /// </summary>
    public static bool IsNullOrWhiteSpace(this string value) => string.IsNullOrWhiteSpace(value);

    /// <summary>
    /// string.IsNullOrWhiteSpace 的简化方法(逆向)
    /// </summary>
    public static bool IsNotNullOrWhiteSpace(this string value)
        => !value.IsNullOrWhiteSpace();

    /// <summary>
    /// 尝试从 <paramref name="tagName"/> 提取标签 <paramref name="input"/> 之间的值。
    /// <para>示例：</para>
    /// <code>
    /// if("&lt;foo>bar&lt;/foo>".TryExtractValueFromTag("foo",out var v))
    /// {
    ///     Console.Write(v);//输出: "bar"
    /// }
    /// </code>
    /// </summary>
    /// <remarks>注意: 此方法不区分大小写。</remarks>
    public static bool TryExtractValueFromTag(this string input, string tagName, out string value)
    {
        if (input == null)
        {
            value = null;
            return false;
        }
        AssertUtil.NotNull(tagName);

        var pattern = $"<{tagName}[^>]*>(.*)</{tagName}>";
        var match = Regex.Match(input, pattern, RegexOptions.IgnoreCase);

        if (match.Success)
        {
            value = match.Groups[1].ToString();
            return true;
        }

        value = null;
        return false;
    }

    /// <summary>
    /// 示例:
    /// <code>
    /// var list = "jack, tom,,lisa".SplitAndTrim(',');
    /// //输出: ["jack","tom","lisa"]
    /// </code>
    /// </summary>
    /// <remarks>注意: 如果字符串为null, 返回空集合, 而不是报错</remarks>
    public static List<string> SplitAndTrim(this string value, params char[] separators)
    {
        if (value == null) return [];
        return value.Trim()
            .Split(separators, StringSplitOptions.RemoveEmptyEntries)
            .Select(s => s.Trim())
            .ToList();
    }

    /// <summary>
    /// 示例:
    /// <code>
    /// var list = "jack, tom,,lisa".SplitAndTrim(",");
    /// //输出: ["jack","tom","lisa"]
    /// </code>
    /// </summary>
    /// <remarks>注意: 如果字符串为null, 返回空集合, 而不是报错</remarks>
    public static List<string> SplitAndTrim(this string value, params string[] separators)
    {
        if (value == null) return [];
        return value.Trim()
            .Split(separators, StringSplitOptions.RemoveEmptyEntries)
            .Select(s => s.Trim())
            .ToList();
    }

    /// <summary>
    /// 应用举例: 
    /// <code>
    /// "1,2,4".SplitAndTrimTo&lt;int&gt;(",")
    /// //输出: [1,2,4]
    /// </code>
    /// </summary>
    /// <remarks>注意: 如果字符串为null, 返回空集合, 而不是报错</remarks>
    public static List<T> SplitAndTrimTo<T>(this string value, params string[] separators)
    {
        if (value == null) return [];
        var arr = SplitAndTrim(value, separators);
        var res = new List<T>(arr.Count);
        arr.ForEach(i => res.Add(i.To<T>()));
        return res;
    }
    /// <summary>
    /// 示例:
    /// <code>
    /// "hello tom!".Contains("Tom", StringComparison.OrdinalIgnoreCase).ShouldBe(true)
    /// </code>
    /// </summary>
    public static bool Contains(this string input, string stringSegement, StringComparison comparison = StringComparison.OrdinalIgnoreCase) =>
        input.Contains(stringSegement, comparison);

    /// <summary>
    /// 示例:
    /// <code>
    /// "abc".EqualsAnyIgnoreCase("ABC", "def").ShouldBeTrue();
    /// </code>
    /// </summary>
    public static bool EqualsAnyIgnoreCase(this string input, params string[] stringSegement) => stringSegement.Any(x => StringComparer.OrdinalIgnoreCase.Equals(x, input));

    /// <summary>
    /// 示例:
    /// <code>
    /// "abc".EqualsAny("abc", "def").ShouldBeTrue();
    /// "abc".EqualsAny("ABC", "def").ShouldBeFalse();
    /// </code>
    /// </summary>
    public static bool EqualsAny(this string input, params string[] stringSegement) => stringSegement.Any(x => StringComparer.Ordinal.Equals(x, input));

    /// <summary>
    /// 示例:
    /// <code>
    /// "1234567".Truncate(2).ShouldBe("12");
    /// "1234567".Truncate(2, "...").ShouldBe("12...");
    /// </code>
    /// </summary>
    /// <remarks>注意:
    /// <list type="bullet">
    /// <item>当字符串为null时, 返回null</item>
    /// <item>当maxLength&lt;0时, 返回字符串本身</item>
    /// </list>
    /// </remarks>
    public static string Truncate(this string input, int maxLength, string suffix = "")
    {
        if (input == null) return input;
        if (maxLength < 0 || input.Length <= maxLength) { return input; }
        if (maxLength == 0) { return string.Empty; }

        if (suffix != null) return new string(input.Take(maxLength).Concat(suffix).ToArray());
        else return new string(input.Take(maxLength).ToArray());
    }

    /// <summary>
    /// 移除 '\r' 和 '\n', 示例:
    /// <code>
    /// "\r\n\nHe\rllo\r\r\n".RemoveNewLines().ShouldBe("Hello");
    /// </code>
    /// </summary>
    /// <remarks>注意: 当字符串为null时, 返回null, 而不是报错</remarks>
    public static string RemoveNewLines(this string input)
    {
        if (input == null) return input;
        return input.Replace("\n", string.Empty).Replace("\r", string.Empty);
    }

    /// <summary>
    /// 与另一个字符串比较(其实就是: string.Equals(input, target) 简写),　示例:
    /// <code>
    /// "tom".IsEqualTo("tom").ShouldBe(true);
    /// "tom".IsEqualTo("TOM",true).ShouldBe(true);
    /// </code>
    /// </summary>
    /// <remarks>
    /// 注意:
    /// <list type="bullet">
    /// <item>两者都为null时, 相等</item>
    /// <item>只有一个为null时, 不相等</item>
    /// </list>
    /// </remarks>
    public static bool IsEqualTo(this string input, string target, bool ignoreCase = false) => string.Equals(input, target, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);

    /// <summary>
    /// 将字符串中的英文数字转为中文, <br/>其实就是: str.Replace("0","零").Replace("1","一")..., <br/>示例:
    /// <code>
    /// "1997年, 香港回归了.".ToChineseNumber();
    /// //输出: "一九九七年, 香港回归了."
    /// </code>
    /// </summary>
    /// <remarks>注意: 如果字符串为null, 返回null, 而不是报错</remarks>
    public static string ToChineseNumber(this string input)
    {
        if (input == null) return input;
        return input
            .Replace("0", "零")
            .Replace("1", "一")
            .Replace("2", "二")
            .Replace("3", "三")
            .Replace("4", "四")
            .Replace("5", "五")
            .Replace("6", "六")
            .Replace("7", "七")
            .Replace("8", "八")
            .Replace("9", "九");
    }

    #region 压缩/解压缩字符串
    /// <summary>
    /// 使用 Gzip 算法压缩字符串到字节数组(utf-8编码)
    /// </summary>
    /// <remarks>注意: 如果字符串为null, 返回null, 而不是报错</remarks>
    public static byte[] Compress(this string input)
    {
        if (input == null) return null;
        var buffer = Encoding.UTF8.GetBytes(input);
        using var memStream = new MemoryStream();
        using var zipStream = new GZipStream(memStream, CompressionMode.Compress, true);

        zipStream.Write(buffer, 0, buffer.Length);
        zipStream.Close();
        return memStream.ToArray();
    }

    /// <summary>
    /// 压缩字符串并以base64格式输出
    /// </summary>
    /// <remarks>注意: 如果字符串为null, 返回null, 而不是报错</remarks>
    public static string CompressToBase64(this string input)
    {
        if (input == null) return input;
        var bs = Compress(input);
        return Convert.ToBase64String(bs);
    }

    /// <summary>
    /// 压缩字符串并以base64UrlSafe格式输出
    /// </summary>
    /// <remarks>注意: 如果字符串为null, 返回null, 而不是报错</remarks>
    public static string CompressToBase64UrlSafe(this string input)
    {
        if (input == null) return input;
        var bs = Compress(input);
        return Base64UrlSafe.Encode(bs);
    }

    /// <summary>
    /// 解压缩 <seealso cref="CompressToBase64(string)"/> 和 <seealso cref="CompressToBase64UrlSafe(string)"/> 字符串
    /// </summary>
    /// <remarks>注意: 如果字符串为null, 返回null, 而不是报错</remarks>
    public static string Decompress(this string compressedInput)
    {
        if (compressedInput == null) return null;
        var bs = Base64UrlSafe.Decode(compressedInput);

        using var originalStream = new MemoryStream(bs);
        using var decompressedStream = new MemoryStream();
        using (var decompressionStream = new GZipStream(originalStream, CompressionMode.Decompress))
        {
            decompressionStream.CopyTo(decompressedStream);
        }
        return Encoding.UTF8.GetString(decompressedStream.ToArray());
    }
    #endregion

    /// <summary>
    /// 返回字符串占用的字节数, 其实就是: input.Length * sizeof(char);
    /// </summary>
    /// <remarks>注意: 如果字符串为null,则返回0</remarks>
    public static int GetSize(this string input) => input == null ? 0 : input.Length * sizeof(char);

    #region ToObject
    private static Func<string, object> jobjectFunc = null;
    private static Func<string, object> getJObjectParseFunc(Assembly assembly)
    {
        if (jobjectFunc == null)
        {
            var tmp = assembly.GetType("Newtonsoft.Json.Linq.JObject");
            var parseMehtod = tmp.GetMethods().FirstOrDefault(i => i.Name == "Parse" && i.GetParameters().Length == 2);
            var tmp2 = assembly.GetType("Newtonsoft.Json.Linq.JsonLoadSettings");
            var para = Expression.Parameter(typeof(string), "input");
            var ctor = tmp2.GetConstructors().FirstOrDefault();
            //数字 枚举转换
            var enumType = assembly.GetType("Newtonsoft.Json.Linq.CommentHandling");

            var memberInit = Expression.MemberInit(Expression.New(ctor), Expression.Bind(tmp2.GetProperty("CommentHandling"), Expression.Convert(Expression.Constant(0), enumType)));
            jobjectFunc = Expression.Lambda<Func<string, object>>(Expression.Call(null, parseMehtod, para, memberInit), para).Compile();
        }
        return jobjectFunc;
    }

    private static Func<string, object> jarrFunc = null;
    private static Func<string, object> getJArrayParseFunc(Assembly assembly)
    {
        if (jarrFunc == null)
        {
            var tmp = assembly.GetType("Newtonsoft.Json.Linq.JArray");
            var parseMehtod = tmp.GetMethods().FirstOrDefault(i => i.Name == "Parse" && i.GetParameters().Length == 2);
            var tmp2 = assembly.GetType("Newtonsoft.Json.Linq.JsonLoadSettings");
            var para = Expression.Parameter(typeof(string), "input");
            var ctor = tmp2.GetConstructors().FirstOrDefault();
            //数字 枚举转换
            var enumType = assembly.GetType("Newtonsoft.Json.Linq.CommentHandling");
            var memberInit = Expression.MemberInit(Expression.New(ctor), Expression.Bind(tmp2.GetProperty("CommentHandling"), Expression.Convert(Expression.Constant(0), enumType)));
            jarrFunc = Expression.Lambda<Func<string, object>>(Expression.Call(null, parseMehtod, para, memberInit), para).Compile();
        }
        return jarrFunc;
    }

    private static Func<string, Type, object> deserializeObjectFunc = null;
    private static Func<string, Type, object> getDeserializeObjectFunc(Assembly assembly)
    {
        if (deserializeObjectFunc == null)
        {
            var tmp = assembly.GetType("Newtonsoft.Json.JsonConvert");
            var parseMehtod = tmp.GetMethods().FirstOrDefault(i => i.Name == "DeserializeObject" && i.GetParameters().Length == 2 && i.GetParameters()[1].ParameterType == typeof(Type));
            var paraInput = Expression.Parameter(typeof(string), "input");
            var paraType = Expression.Parameter(typeof(Type), "type");
            deserializeObjectFunc = Expression.Lambda<Func<string, Type, object>>(Expression.Call(null, parseMehtod, paraInput, paraType), paraInput, paraType).Compile();
        }
        return deserializeObjectFunc;
    }

    /// <summary>
    /// 使用json的反序列化将字符串转为对象
    /// </summary>
    /// <remarks>
    /// 注意: 
    /// <list type="bullet">
    /// <item>aot 模式下需要先自行在项目中引用 NewtonSoft.Json.dll 因为 DotNetCommon 本身不直接引用它, 但是会用反射加载它</item>
    /// </list>
    /// </remarks>
    public static T ToObject<T>(this string input)
    {
        if (input == null && default(T) == null) return default;

        var type = typeof(T);
        if (Machine.IsRunningInAotMode())
        {
            #region aot模式
            if (type == typeof(JsonNode)) return (T)(object)JsonNode.Parse(input);
            if (type == typeof(JsonArray)) return (T)(object)JsonArray.Parse(input);
            if (type == typeof(JsonObject)) return (T)(object)JsonObject.Parse(input);
            if (type == typeof(JsonDocument)) return (T)(object)JsonDocument.Parse(input);
            if (type == typeof(JsonTreeNode)) return (T)(object)JsonTreeNode.Parse(input);

            Func<string, Type, object> act;
            if (deserializeObjectFunc != null)
            {
                act = deserializeObjectFunc;
            }
            else
            {
                try
                {
                    act = getDeserializeObjectFunc(Type.GetType("Newtonsoft.Json.JsonConvert, Newtonsoft.Json").Assembly);
                }
                catch (Exception e)
                {
                    throw new Exception($"在aot模式下ToObject需要改用 Newtonsoft.Json, 但加载失败, 请自行在项目中引用 Newtonsoft.Json 程序集", e);
                }
            }
            return (T)act(input, typeof(T));
            #endregion
        }
        else
        {
            //兼容 JObject 和 JArray 的 ToObject
            if (type.FullName == "Newtonsoft.Json.Linq.JObject")
            {
                var act = getJObjectParseFunc(typeof(T).Assembly);
                return (T)act(input);
            }
            if (type.FullName == "Newtonsoft.Json.Linq.JArray")
            {
                var act = getJArrayParseFunc(typeof(T).Assembly);
                return (T)act(input);
            }
            if (type == typeof(JsonTreeNode)) return (T)(object)JsonTreeNode.Parse(input);
            //使用 System.Text.Json
            var options = JsonHelper._getDefaultJsonSerializerOptions(readOnly: true);
            return JsonSerializer.Deserialize<T>(input, options);
        }
    }

    /// <summary>
    /// 使用json的反序列化将字符串转为对象
    /// </summary>
    /// <remarks>
    /// 注意: 
    /// <list type="bullet">
    /// <item>aot 模式下将改用 NewtonSoft, 此时传入的 <c>options</c> 将会被忽略</item>
    /// <item>aot 模式下需要先自行在项目中引用 NewtonSoft.Json.dll 因为 DotNetCommon 本身不直接引用它, 但是会用反射加载它</item>
    /// </list>
    /// </remarks>
    public static T ToObject<T>(this string input, JsonSerializerOptions options)
    {
        if (input == null && default(T) == null) return default;

        var type = typeof(T);
        if (Machine.IsRunningInAotMode())
        {
            #region aot模式
            if (type == typeof(JsonNode)) return (T)(object)JsonNode.Parse(input);
            if (type == typeof(JsonArray)) return (T)(object)JsonArray.Parse(input);
            if (type == typeof(JsonObject)) return (T)(object)JsonObject.Parse(input);
            if (type == typeof(JsonDocument)) return (T)(object)JsonDocument.Parse(input);
            if (type == typeof(JsonTreeNode)) return (T)(object)JsonTreeNode.Parse(input);

            Func<string, Type, object> act;
            if (deserializeObjectFunc != null)
            {
                act = deserializeObjectFunc;
            }
            else
            {
                try
                {
                    act = getDeserializeObjectFunc(Type.GetType("Newtonsoft.Json.JsonConvert, Newtonsoft.Json").Assembly);
                }
                catch (Exception e)
                {
                    throw new Exception($"在aot模式下ToObject需要改用 Newtonsoft.Json, 但加载失败, 请自行在项目中引用 Newtonsoft.Json 程序集", e);
                }
            }
            return (T)act(input, typeof(T));
            #endregion
        }
        else
        {
            //兼容 JObject 和 JArray 的 ToObject
            if (type.FullName == "Newtonsoft.Json.Linq.JObject")
            {
                var act = getJObjectParseFunc(typeof(T).Assembly);
                return (T)act(input);
            }
            if (type.FullName == "Newtonsoft.Json.Linq.JArray")
            {
                var act = getJArrayParseFunc(typeof(T).Assembly);
                return (T)act(input);
            }
            if (type == typeof(JsonTreeNode)) return (T)(object)JsonTreeNode.Parse(input);
            //使用 System.Text.Json
            options ??= JsonHelper._getDefaultJsonSerializerOptions(readOnly: true);
            return JsonSerializer.Deserialize<T>(input, options);
        }
    }

    /// <summary>
    /// 使用json的反序列化将字符串转为对象
    /// </summary>
    /// <remarks>
    /// 注意: 
    /// <list type="bullet">
    /// <item>aot 模式下将改用 NewtonSoft, 此时传入的 <c>action</c> 将会被忽略</item>
    /// <item>aot 模式下需要先自行在项目中引用 NewtonSoft.Json.dll 因为 DotNetCommon 本身不直接引用它, 但是会用反射加载它</item>
    /// </list>
    /// </remarks>
    public static T ToObject<T>(this string input, Action<JsonSerializerOptions> action)
    {
        if (input == null && default(T) == null) return default;

        var type = typeof(T);
        if (Machine.IsRunningInAotMode())
        {
            #region aot模式
            if (type == typeof(JsonNode)) return (T)(object)JsonNode.Parse(input);
            if (type == typeof(JsonArray)) return (T)(object)JsonArray.Parse(input);
            if (type == typeof(JsonObject)) return (T)(object)JsonObject.Parse(input);
            if (type == typeof(JsonDocument)) return (T)(object)JsonDocument.Parse(input);
            if (type == typeof(JsonTreeNode)) return (T)(object)JsonTreeNode.Parse(input);

            Func<string, Type, object> act;
            if (deserializeObjectFunc != null)
            {
                act = deserializeObjectFunc;
            }
            else
            {
                try
                {
                    act = getDeserializeObjectFunc(Type.GetType("Newtonsoft.Json.JsonConvert, Newtonsoft.Json").Assembly);
                }
                catch (Exception e)
                {
                    throw new Exception($"在aot模式下ToObject需要改用 Newtonsoft.Json, 但加载失败, 请自行在项目中引用 Newtonsoft.Json 程序集", e);
                }
            }
            return (T)act(input, typeof(T));
            #endregion
        }
        else
        {
            //兼容 JObject 和 JArray 的 ToObject
            if (type.FullName == "Newtonsoft.Json.Linq.JObject")
            {
                var act = getJObjectParseFunc(typeof(T).Assembly);
                return (T)act(input);
            }
            if (type.FullName == "Newtonsoft.Json.Linq.JArray")
            {
                var act = getJArrayParseFunc(typeof(T).Assembly);
                return (T)act(input);
            }
            if (type == typeof(JsonTreeNode)) return (T)(object)JsonTreeNode.Parse(input);
            //使用 System.Text.Json
            var options = JsonHelper._getDefaultJsonSerializerOptions(readOnly: action == null);
            return JsonSerializer.Deserialize<T>(input, options);
        }
    }
    #endregion

    /// <summary>
    /// 将当前字符串重复指定次数
    /// </summary>
    /// <remarks>注意: 如果字符串为null, 则返回null</remarks>
    public static string Repeat(this string str, int count)
    {
        if (str == null) return null;
        if (count <= 1) return str;
        var sb = new StringBuilder();
        for (var i = 0; i < count; i++) sb.Append(str);
        return sb.ToString();
    }

    #region IsMatchRegex & MatchAndExtracts
    /// <summary>
    /// 是否匹配正则表达式, 示例:
    /// <code>
    /// "abc1234def".IsMatchRegex(new Regex(@"\d+")).ShouldBe(true);
    /// "abc1234def".IsMatchRegex(new Regex(@"^\d+$")).ShouldBe(false);
    /// </code>
    /// </summary>
    public static bool IsMatchRegex(this string str, Regex regex)
    {
        AssertUtil.NotNull(str);
        AssertUtil.NotNull(regex);
        return regex.IsMatch(str);
    }

    /// <summary>
    /// 是否匹配正则表达式, 示例:
    /// <code>
    /// "abc1234def".IsMatchRegex(@"\d+") => true
    /// "xxxx".IsMatchRegex(@"^\d{9,11}$") => false
    /// </code>
    /// </summary>
    public static bool IsMatchRegex(this string str, string regexPattern)
    {
        AssertUtil.NotNull(str);
        AssertUtil.NotNullOrWhiteSpace(regexPattern);
        return new Regex(regexPattern).IsMatch(str);
    }

    /// <summary>
    /// 根据正则表达式提取 "()" 捕获的字符串, 示例:
    /// <code>
    /// "apple 123, banana 456, cherry 789".MatchAndExtracts(new Regex(@"\w+ \d+")) => []
    /// "apple 123, banana 456, cherry 789".MatchAndExtracts(new Regex(@"(\w+) \d+")) => ["apple", "banana", "cherry"]
    /// "apple 123, banana 456, cherry 789".MatchAndExtracts(new Regex(@"(\w+ \d+)")) => ["apple 123", "banana 456", "cherry 789"]
    /// "apple 123, banana 456, cherry 789".MatchAndExtracts(new Regex(@"(\w+) (\d+)")) => ["apple", "123", "banana", "456", "cherry", "789"]
    /// </code>
    /// </summary>
    public static List<string> MatchAndExtracts(this string str, Regex regex)
    {
        AssertUtil.NotNull(str);
        AssertUtil.NotNull(regex);
        return MatchAndExtractsInternal(str, regex);
    }

    /// <summary>
    /// 根据正则表达式提取字符串, 相比 <see cref="MatchAndExtracts(string, Regex)"/> 这个只返回第一个, 示例:
    /// <code>
    /// "apple 123, banana 456, cherry 789".MatchAndExtract(new Regex(@"\w+ \d+")) => null
    /// "apple 123, banana 456, cherry 789".MatchAndExtract(new Regex(@"(\w+) \d+")) => "apple"
    /// "apple 123, banana 456, cherry 789".MatchAndExtract(new Regex(@"(\w+ \d+)")) => "apple 123"
    /// "apple 123, banana 456, cherry 789".MatchAndExtract(new Regex(@"(\w+) (\d+)")) => "apple"
    /// </code>
    /// </summary>
    public static string MatchAndExtract(this string str, Regex regex)
    {
        AssertUtil.NotNull(str);
        AssertUtil.NotNull(regex);
        return MatchAndExtractInternal(str, regex);
    }

    /// <summary>
    /// 根据正则表达式提取 "()" 捕获的字符串, 示例:
    /// <code>
    /// "apple 123, banana 456, cherry 789".MatchAndExtracts(@"\w+ \d+") => []
    /// "apple 123, banana 456, cherry 789".MatchAndExtracts(@"(\w+) \d+") => ["apple", "banana", "cherry"]
    /// "apple 123, banana 456, cherry 789".MatchAndExtracts(@"(\w+ \d+)") => ["apple 123", "banana 456", "cherry 789"]
    /// "apple 123, banana 456, cherry 789".MatchAndExtracts(@"(\w+) (\d+)") => ["apple", "123", "banana", "456", "cherry", "789"]
    /// </code>
    /// </summary>
    public static List<string> MatchAndExtracts(this string str, string regexPattern)
    {
        AssertUtil.NotNull(str);
        AssertUtil.NotNullOrWhiteSpace(regexPattern);
        return MatchAndExtractsInternal(str, new Regex(regexPattern));
    }

    /// <summary>
    /// 根据正则表达式提取字符串, 相比 <see cref="MatchAndExtracts(string, string)"/> 这个只返回第一个, 示例:
    /// <code>
    /// "apple 123, banana 456, cherry 789".MatchAndExtract(@"\w+ \d+") => null
    /// "apple 123, banana 456, cherry 789".MatchAndExtract(@"(\w+) \d+") => "apple"
    /// "apple 123, banana 456, cherry 789".MatchAndExtract(@"(\w+ \d+)") => "apple 123"
    /// "apple 123, banana 456, cherry 789".MatchAndExtract(@"(\w+) (\d+)") => "apple"
    /// </code>
    /// </summary>
    public static string MatchAndExtract(this string str, string regexPattern)
    {
        AssertUtil.NotNull(str);
        AssertUtil.NotNullOrWhiteSpace(regexPattern);
        return MatchAndExtractInternal(str, new Regex(regexPattern));
    }

    private static string MatchAndExtractInternal(string str, Regex regex)
    {
        var match = regex.Match(str);
        if (!match.Success) return null;
        var idx = 0;
        foreach (Group capture in match.Groups)
        {
            idx++;
            if (idx == 2) return capture.Value;
        }
        return null;
    }

    private static List<string> MatchAndExtractsInternal(string str, Regex regex)
    {
        var matches = regex.Matches(str);
        var res = new List<string>();
        foreach (var item in matches.Where(i => i.Success && i.Groups.Count > 1))
        {
            var idx = 0;
            foreach (Group group in item.Groups)
            {
                idx++;
                if (idx > 1) res.Add(group.Value);
            }
        }
        return res;
    }
    #endregion

    #region 身份证号码
    private static readonly string[] ChinaIDProvinceCodes = {
         "11", "12", "13", "14", "15",
         "21", "22", "23",
         "31", "32", "33", "34", "35", "36", "37",
         "41", "42", "43", "44", "45", "46",
         "50", "51", "52", "53", "54",
         "61", "62", "63", "64", "65",
         "71",
         "81", "82",
         "91"
    };

    /// <summary>
    /// 是否是有效的第二代身份证号(18位)
    /// </summary>
    /// <remarks>
    /// 注意: 省份不对的, 出生日期在 1800 年以前的都将认为无效
    /// </remarks>
    public static bool IsChinaIdCard18(this string str)
    {
        if (str.IsNullOrWhiteSpace()) return false;
        str = str.ToUpper();
        var m = Regex.Match(str, @"\d{17}[\dX]", RegexOptions.IgnoreCase);
        if (!m.Success) return false;
        if (!ChinaIDProvinceCodes.Contains(str[..2])) return false;
        var zhCN = new CultureInfo("zh-CN", true);
        if (!DateTime.TryParseExact(str.AsSpan(6, 8), "yyyyMMdd", zhCN, DateTimeStyles.None, out DateTime birthday)) return false;
        if (!birthday.IsBetween(new DateTime(1800, 1, 1), DateTime.Today)) return false;
        int[] factors = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 };
        int sum = 0;
        for (int i = 0; i < 17; i++)
        {
            sum += (str[i] - '0') * factors[i];
        }
        int n = (12 - sum % 11) % 11;
        return n < 10 ? str[17] - '0' == n : str[17].Equals('X');
    }

    /// <summary>
    /// 是否是第一代身份证号(15位)
    /// </summary>
    public static bool IsChinaIdCard15(string str)
    {
        if (str.IsNullOrWhiteSpace()) return false;
        var m = Regex.Match(str, @"\d{15}", RegexOptions.IgnoreCase);
        if (!m.Success) return false;
        if (!ChinaIDProvinceCodes.Contains(str[..2])) return false;
        CultureInfo zhCN = new("zh-CN", true);
        if (!DateTime.TryParseExact(string.Concat("19", str.AsSpan(6, 6)), "yyyyMMdd", zhCN, DateTimeStyles.None, out DateTime birthday)) return false;
        return birthday.IsBetween(new DateTime(1800, 1, 1), new DateTime(2000, 1, 1));
    }
    #endregion
    #region IP地址
    /// <summary>
    /// 是否是IP地址, 其实就是: IPAddress.TryParse(str, out _)
    /// </summary>
    /// <remarks>注意: 如果字符串为空, 返回false, 而不是报错</remarks>
    public static bool IsIP(this string str)
    {
        if (str.IsNullOrWhiteSpace() == false) return false;
        return IPAddress.TryParse(str, out _);
    }
    /// <summary>
    /// 是否是私有地址
    /// </summary>
    /// <remarks>注意: 如果字符串为空, 返回false, 而不是报错</remarks>
    public static bool IsPrivateIP(this string str)
    {
        if (str.IsNullOrWhiteSpace() == false) return false;
        return IPAddress.TryParse(str, out var ip) && ip.IsPrivateIP();
    }
    /// <summary>
    /// IP地址转换成数字
    /// </summary>
    /// <returns>数字,输入无效IP地址返回0</returns>
    public static uint ConvertIPToNumber(this string addr)
    {
        if (addr.IsNullOrWhiteSpace()) return 0;
        if (!IPAddress.TryParse(addr, out var ip)) return 0;

        byte[] bInt = ip.GetAddressBytes();
        if (BitConverter.IsLittleEndian) Array.Reverse(bInt);
        return BitConverter.ToUInt32(bInt, 0);
    }
    /// <summary>
    /// 判断IP地址在不在某个IP地址段, 示例:
    /// <code>
    /// "192.168.2.5".IsIpAddressInRange("192.168.0.0", "192.168.255.255");
    /// //输出: true
    /// </code>
    /// </summary>
    /// <remarks>注意: 任何一个字符串为空, 返回false, 而不是报错</remarks>
    public static bool IsIpAddressInRange(this string input, string begin, string ends)
    {
        if (input.IsNullOrWhiteSpace() || begin.IsNullOrWhiteSpace() || ends.IsNullOrWhiteSpace()) return false;
        uint current = ConvertIPToNumber(input);
        return current >= ConvertIPToNumber(begin) && current <= ConvertIPToNumber(ends);
    }
    #endregion
    #region 手机号
    /// <summary>
    /// 是否是手机号(最宽松的: 11位, 以 '1' 开头即可, 另外: 兼容+区号), 示例:
    /// <code>
    /// "12345678901".IsPhoneNumber().ShouldeBe(true)
    /// "+8612345678901".IsPhoneNumber().ShouldeBe(true)
    /// "+86 12345678901".IsPhoneNumber().ShouldeBe(true)
    /// </code>
    /// </summary>
    public static bool IsPhoneNumber(this string str)
    {
        if (str.IsNullOrWhiteSpace()) return false;
        if (str.StartsWith('+'))
        {
            foreach (var item in CellPhonePrefix.Prefix)
            {
                if (str.StartsWith(item))
                {
                    str = str.Substring(item.Length).Trim();
                    break;
                }
            }
        }
        var match = Regex.Match(str, @"^1\d{10}$");
        return match.Success;
    }
    #endregion
    #region 邮箱
    public static bool IsValidEmail(this string str)
    {
        return RegexHelper.IsValidEmail(str);
    }
    #endregion
}
